import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class main extends JFrame implements ActionListener {
    private BufferedImage bgImage;  // 用于存储背景图片
    public static String fileskin = new String();  // 用于存储背景图片文件名

    // 重写paint方法，绘制背景图片
    public void paint(Graphics g) {
        g.drawImage(bgImage, 0, 0, this.getWidth(), this.getHeight(), this);
    }

    public static String randomwalklocate = "random.txt";  // 随机游走存储路径
    public static String finalstr0 = new String();  // 存储处理后的文本
    public static Graph G0;  // 图对象

    // 清屏方法，模拟按键和鼠标操作
    public static void clear() throws AWTException {
        Robot r = new Robot();
        r.mousePress(InputEvent.BUTTON3_MASK);       // 按下鼠标右键
        r.mouseRelease(InputEvent.BUTTON3_MASK);     // 释放鼠标右键
        r.keyPress(KeyEvent.VK_CONTROL);             // 按下Ctrl键
        r.keyPress(KeyEvent.VK_R);                   // 按下R键
        r.keyRelease(KeyEvent.VK_R);                 // 释放R键
        r.keyRelease(KeyEvent.VK_CONTROL);           // 释放Ctrl键
        r.delay(100);                                // 延迟100毫秒
    }

    // 主方法，程序入口
    @SuppressWarnings("checkstyle:WhitespaceAfter")
    public static void main(String[] args) throws Exception {
        main mf = new main();  // 创建主框架对象
        fileskin = "background.jpg";  // 背景图片文件名
        mf.bgImage = ImageIO.read(new File(fileskin));  // 读取背景图片
        mf.setTitle("测试");  // 设置窗口标题
        mf.setBounds(300, 100, 750, 950);  // 设置窗口大小和位置
        mf.setBackground(Color.GRAY);  // 设置窗口背景颜色
        mf.setResizable(false);  // 设置窗口不可调整大小
        mf.setVisible(true);  // 显示窗口
        mf.setLayout(null);  // 设置布局管理器为空（绝对布局）

        // 添加窗口关闭事件监听器
        mf.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);  // 关闭窗口时退出程序
            }
        });

        // 文件选择按钮及其标签
        JButton buttonreadfile = new JButton("File Open");
        buttonreadfile.setBackground(Color.white);  // 设置按钮背景色
        buttonreadfile.setBounds(30, 40, 100, 50);  // 设置按钮位置和大小
        mf.add(buttonreadfile);  // 将按钮添加到主框架
        Label labelreadfile = new Label();
        labelreadfile.setText("File Address");  // 设置标签文本
        labelreadfile.setBackground(Color.GRAY);  // 设置标签背景色
        labelreadfile.setFont(new Font("宋体", Font.PLAIN, 25));  // 设置标签字体
        labelreadfile.setBounds(130, 40, 450, 50);  // 设置标签位置和大小
        mf.add(labelreadfile);  // 将标签添加到主框架

        // 文本预处理部分的按钮及其标签
        Label labelstrdeal=new Label();
        labelstrdeal.setText("Text File Preprocessing");
        labelstrdeal.setBackground(Color.GRAY);
        labelstrdeal.setFont(new Font("宋体",Font.PLAIN,25));
        labelstrdeal.setBounds(100, 130, 500, 50);
        mf.add(labelstrdeal);
        Button buttonstrdeal=new Button("Run");
        buttonstrdeal.setBounds(30, 130, 70, 50);
        buttonstrdeal.setBackground(Color.white);
        mf.add(buttonstrdeal);

        // 显示邻接表部分的按钮及其标签
        Label labelLink=new Label();
        labelLink.setText("Show Adjacency Table");
        labelLink.setBackground(Color.GRAY);
        labelLink.setFont(new Font("宋体",Font.PLAIN,25));
        labelLink.setBounds(100, 220, 500, 50);
        mf.add(labelLink);
        Button buttonLink=new Button("Run");
        buttonLink.setBounds(30, 220, 70, 50);
        buttonLink.setBackground(Color.WHITE);
        mf.add(buttonLink);

        // 显示有向图部分的按钮及其标签
        Label labelgraph=new Label("Show Directed Graph");
        labelgraph.setBackground(Color.GRAY);
        labelgraph.setFont(new Font("宋体",Font.PLAIN,25));
        labelgraph.setBounds(100, 310,500, 50);
        mf.add(labelgraph);
        Button buttongraph=new Button("Run");
        buttongraph.setBounds(30, 310, 70, 50);
        buttongraph.setBackground(Color.WHITE);
        mf.add(buttongraph);

        // 桥接词部分的按钮及其标签
        Label labelbridge=new Label("Show Bridge Words");
        labelbridge.setBackground(Color.GRAY);
        labelbridge.setFont(new Font("宋体",Font.PLAIN,25));
        labelbridge.setBounds(100, 400,500, 25);
        mf.add(labelbridge);

        Label labelbridgeword1=new Label("Word1");
        Label labelbridgeword2=new Label("Word2");
        labelbridgeword1.setBackground(Color.GRAY);
        labelbridgeword1.setFont(new Font("宋体",Font.PLAIN,20));
        labelbridgeword1.setBounds(100, 425, 250, 25);
        labelbridgeword2.setBackground(Color.GRAY);
        labelbridgeword2.setFont(new Font("宋体",Font.PLAIN,20));
        labelbridgeword2.setBounds(350, 425, 250, 25);
        mf.add(labelbridgeword1);
        mf.add(labelbridgeword2);

        TextField textfieldbridgeword1=new TextField();
        textfieldbridgeword1.setFont(new Font("宋体",Font.PLAIN,25));
        textfieldbridgeword1.setBounds(100, 450, 250, 50);
        mf.add(textfieldbridgeword1);
        TextField textfieldbridgeword2=new TextField();
        textfieldbridgeword2.setFont(new Font("宋体",Font.PLAIN,25));
        textfieldbridgeword2.setBounds(350, 450, 250, 50);
        mf.add(textfieldbridgeword2);

        Button buttonbridge =new Button("Run");
        buttonbridge.setBounds(30, 400, 70, 100);
        buttonbridge.setBackground(Color.white);
        mf.add(buttonbridge);

        // 生成新文本部分的按钮及其标签
        Button buttonnewstr=new Button("Run");
        buttonnewstr.setBounds(30, 540, 70, 100);
        buttonnewstr.setBackground(Color.WHITE);
        mf.add(buttonnewstr);
        Label labelnewstrinput=new Label("Generate New Text");
        labelnewstrinput.setBackground(Color.GRAY);
        labelnewstrinput.setFont(new Font("宋体",Font.PLAIN,25));
        labelnewstrinput.setBounds(100, 540, 500, 50);
        mf.add(labelnewstrinput);

        TextField textfieldnewstrinput=new TextField();
        textfieldnewstrinput.setFont(new Font("宋体",Font.PLAIN,25));
        textfieldnewstrinput.setBounds(100, 590, 500, 50);
        mf.add(textfieldnewstrinput);

        // 显示最短路径部分的按钮及其标签
        Label labelshortestpath=new Label("Shortest Path");
        labelshortestpath.setBackground(Color.GRAY);
        labelshortestpath.setFont(new Font("宋体",Font.PLAIN,25));
        labelshortestpath.setBounds(100, 680, 500, 25);
        mf.add(labelshortestpath);
        Label labelshortestpathword1=new Label("Word1");
        labelshortestpathword1.setBackground(Color.GRAY);
        labelshortestpathword1.setFont(new Font("宋体",Font.PLAIN,20));
        labelshortestpathword1.setBounds(100, 705, 250, 25);
        mf.add(labelshortestpathword1);
        Label labelshortestpathword2=new Label("Word2");
        labelshortestpathword2.setBackground(Color.GRAY);
        labelshortestpathword2.setFont(new Font("宋体",Font.PLAIN,20));
        labelshortestpathword2.setBounds(350, 705, 250, 25);
        mf.add(labelshortestpathword2);

        TextField textfieldshortestpathword1=new TextField();
        textfieldshortestpathword1.setFont(new Font("宋体",Font.PLAIN,25));
        textfieldshortestpathword1.setBounds(100, 730, 250, 50);
        mf.add(textfieldshortestpathword1);
        TextField textfieldshortestpathword2=new TextField();
        textfieldshortestpathword2.setFont(new Font("宋体",Font.PLAIN,25));
        textfieldshortestpathword2.setBounds(350, 730, 250, 50);
        mf.add(textfieldshortestpathword2);

        Button buttonshortestpath =new Button("Run");
        buttonshortestpath.setBackground(Color.WHITE);
        buttonshortestpath.setBounds(30, 680, 70, 100);
        mf.add(buttonshortestpath);

        // 随机游走部分的按钮及其标签
        TextArea textArea1=new TextArea();
        Label labelrandom=new Label("Random Walk");
        labelrandom.setBackground(Color.GRAY);
        labelrandom.setFont(new Font("宋体",Font.PLAIN,25));
        labelrandom.setBounds(100, 820, 500, 50);
        mf.add(labelrandom);
        Button buttonrandom=new Button("Run");
        buttonrandom.setBackground(Color.WHITE);
        buttonrandom.setBounds(30, 820, 70, 50);
        mf.add(buttonrandom);

        // 按钮的动作监听器，点击按钮时触发相应的事件
        buttonreadfile.addActionListener(e -> {
            StrDeal sd1=new StrDeal();
            String strFile1 = null;
            JFileChooser jfc=new JFileChooser();
            jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES );
            jfc.showDialog(new JLabel(), "选择");
            File file=jfc.getSelectedFile();
            if(file.exists()&&file.isFile())strFile1 =file.getAbsolutePath();
            finalstr0 = null;
            try {
                finalstr0 = sd1.Strdeal(strFile1);// 这里调用strdeal类进行文本处理，处理后的文本存储在finalstr0
            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            if(sd1.Strexit(strFile1)==0) {
                Dialog dialogstr=new Dialog(mf, "NOTICE!");
                dialogstr.setVisible(true);
                dialogstr.setBounds(200, 200,260, 260);
                dialogstr.setLayout(null);
                Label labeldialog=new Label("File read unsuccessful!");
                labeldialog.setBackground(Color.red);
                labeldialog.setBounds(0, 0, 400, 350);
                labeldialog.setFont(new Font("宋体",Font.PLAIN,25));
                dialogstr.add(labeldialog);
                Button buttonnofile=new Button("Run");
                buttonnofile.setBackground(Color.WHITE);
                buttonnofile.setBounds(0, 350, 400, 50);
                dialogstr.add(buttonnofile);
                buttonnofile.addActionListener(e12 -> {
                    // TODO Auto-generated method stub
                    System.exit(0);
                });
            }else {
                Dialog dialogstr=new Dialog(mf, "NOTICE!");
                dialogstr.setVisible(true);
                dialogstr.setBounds(200, 200, 250, 250);
                Label labeldialog=new Label("File read successful!");
                labeldialog.setFont(new Font("宋体",Font.PLAIN,25));
                dialogstr.add(labeldialog);
                dialogstr.addWindowListener(new WindowAdapter() {
                    @Override
                    public void windowClosing(WindowEvent e) {
                        // TODO Auto-generated method stub
                        dialogstr.dispose();
                    }
                });

                // 文本预处理按钮的动作监听器
                buttonstrdeal.addActionListener(e13 -> {
                    // TODO Auto-generated method stub
                    Frame mfstr=new Frame("SHOW");
                    mfstr.setBackground(Color.GRAY);
                    mfstr.setBounds(250, 250, 700, 400);
                    mfstr.setVisible(true);
                    TextArea textArea=new TextArea();
                    textArea.setFont(new Font("宋体",Font.PLAIN,25));
                    textArea.setBounds(50, 20, 600, 360);
                    mfstr.add(textArea);
                    textArea.setText(finalstr0);
                    mfstr.addWindowListener(new WindowAdapter() {
                        @Override
                        public void windowClosing(WindowEvent e13) {
                            // TODO Auto-generated method stub
                            mfstr.dispose();
                        }
                    });
                });

                // 哈希表
                ExerciseHash Ehash0=new ExerciseHash();
                Ehash0.calcletter(finalstr0);

                 // 根据哈希表建立邻接矩阵
                Matrix mt0=new Matrix();
                mt0.Creatematrix(Ehash0, finalstr0);
                mt0.showDirectedGraph();

                // 根据邻接矩阵建立有向图(邻接表)
                String[] splitstr0=finalstr0.split(" ");
                G0=new Graph(mt0.size, splitstr0.length-1, mt0);

                // 最短路径
                int[][] path0=G0.Floyd(mt0);

                // 显示有向图按钮的动作监听器
                buttongraph.addActionListener(e14 -> {
                    // TODO Auto-generated method stub
                    Graphv GG0=new Graphv();
                    String dotFormat0=G0.Createpicture(mt0);
                    GG0.createDotGraph(dotFormat0,"DotGraph");
                    DrawFrame frame0 = new DrawFrame("DotGraph");
                    frame0.pack();
                    frame0.show();
                });

                String[] printstr0=G0.LinklistPrint(mt0);

                // 显示邻接表按钮的动作监听器
                buttonLink.addActionListener(e15 -> {
                    // TODO Auto-generated method stub
                    Frame mflink=new Frame("SHOW");
                    mflink.setBackground(Color.GRAY);
                    mflink.setBounds(200, 200, 400, 400);
                    mflink.setVisible(true);
                    mflink.addWindowListener(new WindowAdapter() {
                        @Override
                        public void windowClosing(WindowEvent e15) {
                            // TODO Auto-generated method stub
                            mflink.dispose();
                        }
                    });
                    TextArea textArea11 =new TextArea();
                    textArea11.setFont(new Font("宋体",Font.PLAIN,25));
                    textArea11.setBounds(50, 50, 300, 300);
                    mflink.add(textArea11);
                    for(int i=0;i<printstr0.length;i++) {
                        textArea11.append(printstr0[i]);
                        textArea11.append("\n");
                    }
                });

                // 显示桥接词按钮的动作监听器
                buttonbridge.addActionListener(e16 -> {
                    // TODO Auto-generated method stub
                    String bridge1st=null,bridge2nd=null;
                    bridge1st=textfieldbridgeword1.getText().toString();
                    bridge2nd=textfieldbridgeword2.getText().toString();
                    String finalbridge=G0.queryBridgeWords(bridge1st, bridge2nd);
                    Frame mfb=new Frame("SHOW");
                    mfb.setBackground(Color.gray);
                    mfb.setBounds(300, 300, 300, 300);
                    TextArea textAreabridgeresult=new TextArea();
                    textAreabridgeresult.setFont(new Font("宋体",Font.PLAIN,25));
                    textAreabridgeresult.setBounds(350, 500, 300, 300);
                    mfb.setVisible(true);
                    mfb.add(textAreabridgeresult);
                    mfb.addWindowListener(new WindowAdapter() {
                        @Override
                        public void windowClosing(WindowEvent e16) {
                            // TODO Auto-generated method stub
                            mfb.dispose();
                        }
                    });
                    if(G0.bridgestrnote[0]==null) {
                        finalbridge="No bridge words from "+bridge1st+" to "+ bridge2nd;
                        textAreabridgeresult.setText(finalbridge);
                    }else if(G0.bridgestrnote[0]=="0"){
                        finalbridge="No "+bridge1st+" in the graph!";
                        textAreabridgeresult.setText(finalbridge);
                    }else if(G0.bridgestrnote[0]=="1"){
                        finalbridge="No "+bridge2nd+" in the graph!";
                        textAreabridgeresult.setText(finalbridge);
                    }else if(G0.bridgestrnote[0]=="2"){
                        finalbridge="No "+bridge1st+" and "+bridge2nd+" in the graph!";
                        textAreabridgeresult.setText(finalbridge);
                    }else {
                        for(int i=0;i<G0.bridgenumnote;i++) {
                            textAreabridgeresult.append(G0.bridgestrnote[i]);
                            textAreabridgeresult.append("\n");
                        }
                    }
                });

                // 生成新文本按钮的动作监听器
                buttonnewstr.addActionListener(e17 -> {
                    // TODO Auto-generated method stub
                    Frame mfnewstr=new Frame("SHOW");
                    mfnewstr.setBounds(230, 230, 400, 400);
                    mfnewstr.setBackground(Color.GRAY);
                    mfnewstr.setVisible(true);
                    mfnewstr.addWindowListener(new WindowAdapter() {
                        @Override
                        public void windowClosing(WindowEvent e17) {
                            // TODO Auto-generated method stub
                            mfnewstr.dispose();
                        }
                    });
                    TextField textfieldnewstroutput=new TextField();
                    textfieldnewstroutput.setFont(new Font("宋体",Font.PLAIN,25));
                    textfieldnewstroutput.setBounds(100, 700, 500, 50);
                    mfnewstr.add(textfieldnewstroutput);

                    String sysinput=textfieldnewstrinput.getText();
                    String[] newsplit=sysinput.split(" ");
                    String[] copystr=new String[newsplit.length];
                    String getstr=new String();
                    copystr=G0.generateNewText(sysinput);

                    for(int i=0;i<copystr.length;i++) {
                        getstr=getstr+(newsplit[i]).toString()+" ";
                        if(copystr[i]!=null) {
                            getstr=getstr+(copystr[i]).toString()+" ";
                        }
                    }
                    getstr+=(newsplit[newsplit.length-1]).toString();
                    textfieldnewstroutput.setText(getstr);
                });

                // 最短路径按钮的动作监听器
                buttonshortestpath.addActionListener(e18 -> {
                    String CalcStart=textfieldshortestpathword1.getText();
                    String CalcEnd=textfieldshortestpathword2.getText();
                    Frame mfshortpath=new Frame("SHOW");
                    mfshortpath.setBackground(Color.GRAY);
                    mfshortpath.setLayout(null);
                    mfshortpath.setBounds(500, 500, 400, 400);
                    mfshortpath.setVisible(true);
                    mfshortpath.addWindowListener(new WindowAdapter() {

                        @Override
                        public void windowClosing(WindowEvent e18) {
                            // TODO Auto-generated method stub
                            mfshortpath.dispose();
                        }
                    });
                    TextArea textArea0=new TextArea();
                    textArea0.setFont(new Font("宋体",Font.PLAIN,25));
                    textArea0.setBounds(50, 50, 300, 300);
                    mfshortpath.add(textArea0);
                    String shortestpath;
                    if(textfieldshortestpathword1.getText()==null||textfieldshortestpathword1.getText().trim().equals("")||textfieldshortestpathword2.getText()==null||textfieldshortestpathword2.getText().trim().equals("")) {
                        shortestpath=null;
                    }else {
                        shortestpath=G0.calcShortestPath(CalcStart, CalcEnd, mt0, path0);//String calcShortestPath(String word1, String word2)：计算两个单词之间的最短路径
                    }
                    if(shortestpath.equals("0")) textArea0.setText(CalcStart+" not exist");
                    else if (shortestpath.equals("1")) textArea0.setText(CalcEnd+" not exist");

                    else if (shortestpath.equals("2")) textArea0.setText(CalcStart+" and "+CalcEnd+" not exist");

                    else {textArea0.setText(shortestpath);

                        if (!CalcStart.equals(CalcEnd)) {//CreateShortestPicture
                            Graphv GG0=new Graphv();
                            String dotFormat0=G0.CreateShortestPicture(CalcStart,CalcEnd,mt0,path0);
                            GG0.createDotGraph(dotFormat0,"ShortDotGraph"+CalcStart+CalcEnd);
                            DrawFrame frame0 = new DrawFrame("ShortDotGraph"+CalcStart+CalcEnd);
                            frame0.pack();
                            frame0.show();
                            frame0.setDefaultCloseOperation(frame0.DISPOSE_ON_CLOSE);
                        }
                        File file1 = new File("ShortDotGraph"+CalcStart+CalcEnd+".jpg");

                        if(G0.shortlength>0) {
                            System.out.println(G0.shortlength);
                            String zdlj="最短路径长度是";
                            zdlj+=G0.shortlength;
                            textArea0.append(zdlj);}
                        else {
                            textArea0.append("No Path!");
                        }
                    }});

                // 随机游走按钮的动作监听器
                buttonrandom.addActionListener(e19 -> {
                    // TODO Auto-generated method stub
                    Frame lmf=new Frame("random");
                    lmf.setBackground(Color.GRAY);
                    lmf.setLayout(null);
                    lmf.setBounds(300, 300, 500, 500);
                    lmf.setVisible(true);
                    lmf.addWindowListener(new WindowAdapter() {
                        @Override
                        public void windowClosing(WindowEvent e19) {
                            // TODO Auto-generated method stub
                            lmf.dispose();
                        }
                    });

                    textArea1.setFont(new Font("宋体",Font.PLAIN,25));
                    textArea1.setBounds(50, 50, 400, 400);
                    lmf.add(textArea1);

                    String dlstr=G0.randomWalk(mt0);
                    System.out.println(dlstr);
                    textArea1.append(dlstr);
                    textArea1.append("\n");
                    dlstr+="\n";
                });
            }
        });
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        // TODO Auto-generated method stub

    }
}

class StrDeal {
    // 检查文件是否存在的方法
    public int Strexit(String filename) {
        File frpt = new File(filename);
        if (!frpt.exists()) {
            return 0; // 文件不存在，返回0
        } else {
            return 1; // 文件存在，返回1
        }
    }

    // 读取文件内容并进行字符串处理的方法
    public String Strdeal(String filename) throws IOException {
        File frpt = new File(filename);
        FileReader fr = null;
        BufferedReader br = null;
        String finalStr = new String();

        if (!frpt.exists()) {
            // 如果文件不存在，直接返回空字符串
            return finalStr;
        } else {
            // 文件存在，开始读取文件内容并存入字符串
            fr = new FileReader(frpt);
            br = new BufferedReader(fr);
            String line;

            // 逐行读取文件内容并拼接为一个字符串，每行末尾添加空格
            while ((line = br.readLine()) != null) {
                finalStr = finalStr.concat(line) + " ";
            }

            // 关闭文件读取流和缓冲流
            try {
                br.close();
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 对读取的字符串进行处理
            String copyStr = new String();
            finalStr = finalStr.trim(); // 去除首尾空格
            finalStr = finalStr.replaceAll("\\pP", " "); // 将标点符号替换为空格
            finalStr = finalStr.toLowerCase(); // 转换为小写

            // 去除连续多余的空格
            int i = 0;
            while (i < (finalStr.length() - 1)) {
                if ((finalStr.charAt(i) == ' ') && (finalStr.charAt(i + 1) == ' ')) {
                    i++;
                } else {
                    copyStr += finalStr.charAt(i);
                    i++;
                }
            }
            copyStr += finalStr.charAt((finalStr.length() - 1)); // 添加最后一个字符
            finalStr = copyStr; // 更新最终处理后的字符串

            // 返回处理后的字符串
            return finalStr;
        }
    }
}

class Matrix {
    int size; // 矩阵的大小
    int[][] maxArray = new int[1000][1000]; // 二维数组，用于存储矩阵元素
    int[] hashvalue = new int[1000]; // 数组，用于存储哈希值
    String[] hashkey = new String[1000]; // 数组，用于存储哈希键（字符串）

    // 创建矩阵的方法，根据传入的哈希表和字符串生成矩阵
    public void Creatematrix(ExerciseHash Ehash, String finalstr) {
        size = Ehash.hash.size(); // 获取哈希表的大小

        // 初始化矩阵元素为0
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                maxArray[i][j] = 0;
            }
        }

        // 将哈希表中的键和值存储到数组中
        String[] splitstr = finalstr.split(" ");
        int i = 0;
        Iterator iter = Ehash.hash.keySet().iterator();
        while (iter.hasNext()) {
            hashkey[i] = iter.next().toString();
            hashvalue[i] = Ehash.hash.get(hashkey[i]);
            i++;
        }

        // 根据字符串数组的内容更新矩阵元素
        for (int j = 0; j < splitstr.length - 1; j++) {
            int intx = 0;
            int inty = 0;

            // 查找字符串在哈希表中的索引位置
            Iterator iter2 = Ehash.hash.keySet().iterator();
            while (iter2.hasNext()) {
                if ((iter2.next().toString()).equals(splitstr[j])) {
                    break;
                }
                intx++;
            }

            Iterator iter3 = Ehash.hash.keySet().iterator();
            while (iter3.hasNext()) {
                if ((iter3.next().toString()).equals(splitstr[j + 1])) {
                    break;
                }
                inty++;
            }

            // 更新矩阵中相应位置的值（表示有向边的数量）
            maxArray[intx][inty] += 1;
        }
    }

    // 显示有向图(邻接表)
    public void showDirectedGraph() {
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                System.out.print(maxArray[i][j] + " ");
            }
            System.out.println();
        }
    }
}

class DrawFrame extends JFrame {
    public DrawFrame(String PName) {
        setTitle("DirectedGraph"); // 设置窗口标题
        ImageIcon img = new ImageIcon(PName + ".jpg"); // 创建图像图标
        JLabel n1 = new JLabel(img); // 创建标签并设置图像
        setLayout(new FlowLayout()); // 设置布局为流式布局
        add(n1); // 将图像添加到窗口
        setSize(WIDTH, HEIGHT); // 设置窗口大小
        DrawPanel panel = new DrawPanel(); // 创建绘图面板
        Container contentPane = getContentPane(); // 获取内容面板
        contentPane.add(panel); // 将绘图面板添加到内容面板
    }
    public static final int WIDTH = 500;
    public static final int HEIGHT = 500;
}

class DrawPanel extends JPanel {
    public void paintComponent(Graphics g) {
        super.paintComponent(g); // 调用父类的方法进行绘制准备
        Font f = new Font("Serif", Font.BOLD, 36); // 创建字体对象
        g.setFont(f); // 设置绘图上下文的字体
        g.setColor(Color.RED); // 设置绘图上下文的颜色
    }
}

class ExerciseHash {
    public HashMap<String, Integer> hash = new HashMap<>();
    public HashMap calcletter(String origan) {
        String[] str = origan.split(" "); // 将输入的字符串按空格分割成单词数组
        for (int i = 0; i < str.length; i++) {
            if (!hash.containsKey(str[i])) { // 如果哈希表中不包含当前单词
                hash.put(str[i], 1); // 将单词作为键，出现次数初始化为1存入哈希表
            } else {
                int counts = hash.get(str[i]); // 如果哈希表中已包含当前单词，获取其出现次数
                hash.put(str[i], counts + 1); // 更新单词的出现次数
            }
        }
        return hash; // 返回存储了单词出现次数的哈希表
    }
}

class Graph {
    int Enum; // 边的数量
    int Vnum; // 节点的数量
    public static int shortlength; // 静态变量，记录最短路径长度
    private static final int INF = Integer.MAX_VALUE; // 表示无穷大的值
    private class Enode {
        int serial; // 目标节点在节点数组中的位置
        Enode nextedge; // 指向下一条边的指针
    }
    private class Vnode {
        String data; // 节点的数据（标识）
        Enode firstedge; // 第一条边的指针
    }
    private Vnode[] Varray; // 节点数组
    public String[] bridgestrnote = new String[100]; // 用于存储桥接词的数组
    public int bridgenumnote; // 桥接词数量

    // 构造函数，初始化图的顶点和边信息
    public Graph(int Vnum, int Enum, Matrix mt) {
        Varray = new Vnode[Vnum]; // 创建顶点数组，大小为顶点个数 Vnum

        // 初始化顶点数组中的每个顶点
        for (int i = 0; i < Vnum; i++) {
            Varray[i] = new Vnode();
            Varray[i].data = mt.hashkey[i]; // 设置顶点数据为对应哈希表中的键值
            Varray[i].firstedge = null; // 初始时，每个顶点的第一个边为空
        }

        // 构建邻接表，根据邻接矩阵 mt.maxArray 构建有向图的边信息
        for (int i = 0; i < Vnum; i++) {
            for (int j = 0; j < Vnum; j++) {
                if (mt.maxArray[i][j] != 0) { // 如果邻接矩阵中存在边
                    int posin = getVnodeposition(mt.hashkey[i]); // 获取起始顶点在顶点数组中的位置
                    int posout = getVnodeposition(mt.hashkey[j]); // 获取终止顶点在顶点数组中的位置
                    Enode enode = new Enode();
                    enode.serial = posout; // 设置边的终止顶点位置

                    // 将边添加到起始顶点的邻接表中
                    if (Varray[posin].firstedge == null) {
                        Varray[posin].firstedge = enode; // 若起始顶点的邻接表为空，则直接将边设为第一个边
                    } else {
                        Linklast(Varray[posin].firstedge, enode); // 否则，将边链接到起始顶点邻接表的末尾
                    }
                }
            }
        }
    }

    // 根据顶点名称 Vname 获取顶点在顶点数组中的位置
    public int getVnodeposition(String Vname) {
        for (int i = 0; i < Varray.length; i++) {
            if (Varray[i].data.equals(Vname)) {
                return i; // 返回顶点位置
            }
        }
        return -1; // 若未找到对应顶点，则返回 -1
    }

    // 将边添加到链表末尾
    public void Linklast(Enode list, Enode enodeadd) {
        Enode pe = list;
        while (pe.nextedge != null) {
            pe = pe.nextedge; // 找到链表末尾
        }
        pe.nextedge = enodeadd; // 将新边添加到链表末尾
    }

    public String[] LinklistPrint(Matrix mt) {
        String[] printstr = new String[Varray.length]; // 创建输出字符串数组
        for (int i = 0; i < Varray.length; i++) {
            printstr[i] = Varray[i].data + " :"; // 设置每个顶点的输出字符串
            Enode pe = Varray[i].firstedge;
            if (pe == null) {
                printstr[i] += "null"; // 如果顶点没有边，输出为 "null"
            } else {
                printstr[i] = printstr[i] + mt.hashkey[pe.serial]; // 输出第一条边的终点顶点数据
                pe = pe.nextedge;
                while (pe != null) {
                    printstr[i] = printstr[i] + ", " + mt.hashkey[pe.serial]; // 输出其他边的终点顶点数据
                    pe = pe.nextedge;
                }
            }
        }
        return printstr; // 返回邻接表的字符串数组
    }

    public String Createpicture(Matrix mt) {
        String picturestr=new String(); // 图形描述字符串
        int size=mt.size;
        for(int i=0;i<size;i++) {
            for(int j=0;j<size;j++) {
                if(mt.maxArray[i][j]!=0) {
                    // 添加边的描述信息到图形描述字符串
                    picturestr=picturestr+mt.hashkey[i]+"->"+mt.hashkey[j]+" [ label = "+mt.maxArray[i][j]+" ]"+";";
                }
            }
        }
        return picturestr; // 返回图形描述字符串
    }

    public String CreateShortestPicture(String word1, String word2, Matrix mt, int[][] path) {
        shortlength = 0; // 初始化最短路径长度为0
        String picturestr = ""; // 图形描述字符串
        picturestr = "ratio = fill;\nnode [style=filled];"; // 设置图形比例和节点样式

        int size = mt.size;
        int[][] colorpath = new int[size][size]; // 颜色路径数组初始化

        // 初始化颜色路径数组
        for (int i = 0; i < mt.size; i++) {
            for (int j = 0; j < mt.size; j++) {
                colorpath[i][j] = 0;
            }
        }

        int begin = 0;
        int over = 0;

        // 找到起始单词对应的顶点位置
        for (int i = 0; i < mt.size; i++) {
            if (word1.equals(mt.hashkey[i])) {
                begin = i;
                break;
            }
        }

        // 找到终止单词对应的顶点位置
        for (int i = 0; i < mt.size; i++) {
            if (word2.equals(mt.hashkey[i])) {
                over = i;
                break;
            }
        }

        int mid = path[begin][over];
        colorpath[begin][mid] = 1;

        // 标记最短路径的颜色路径
        while (mid != over) {
            begin = mid;
            mid = path[begin][over];
            colorpath[begin][mid] = 1;
        }

        // 构建图形描述字符串
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                if (mt.maxArray[i][j] != 0) {
                    if (colorpath[i][j] == 1) {
                        // 添加带颜色的边描述信息
                        picturestr += mt.hashkey[i] + "->" + mt.hashkey[j] + " [color=\"0.649 0.701 0.701\"] [ label = " + mt.maxArray[i][j] + " ];";
                        // 设置顶点颜色
                        picturestr += mt.hashkey[i] + " [color=\"0.408 0.498 1.000\"];";
                        picturestr += mt.hashkey[j] + " [color=\"0.408 0.498 1.000\"];";
                        // 累加最短路径长度
                        shortlength += mt.maxArray[i][j];
                    } else {
                        // 添加普通边描述信息
                        picturestr += mt.hashkey[i] + "->" + mt.hashkey[j] + "  [ label = " + mt.maxArray[i][j] + " ];";
                    }
                }
            }
        }
        return picturestr; // 返回图形描述字符串
    }

    public String queryBridgeWords(String word1, String word2) {
        int flag1 = -1;
        int flag2 = -1;

        // 检查word1和word2是否存在于图中
        for (int i = 0; i < Varray.length; i++) {
            if (word1.equals(Varray[i].data)) {
                flag1 = 1;
            }
            if (word2.equals(Varray[i].data)) {
                flag2 = 1;
            }
        }

        int flagc = -1;
        bridgenumnote = 0; // 桥接词数量初始化为0

        if ((flag1 == 1) && (flag2 == 1)) {
            flagc = 0;
            // 遍历顶点数组，查找word1的邻接顶点，再查找邻接顶点的邻接顶点是否为word2
            for (int i = 0; i < Varray.length; i++) {
                if (word1.equals(Varray[i].data)) {
                    Enode pe = Varray[i].firstedge;
                    while (pe != null) {
                        String bridgestr = Varray[pe.serial].data;
                        for (int j = 0; j < Varray.length; j++) {
                            if (bridgestr.equals(Varray[j].data)) {
                                Enode px = Varray[j].firstedge;
                                while (px != null) {
                                    if (Varray[px.serial].data.equals(word2)) {
                                        flagc = 1; // 找到桥接词
                                        bridgestrnote[bridgenumnote] = bridgestr;
                                        bridgenumnote++; // 桥接词数量加一
                                    }
                                    px = px.nextedge;
                                }
                            }
                        }
                        pe = pe.nextedge;
                    }
                }
            }
        }

        // 根据不同情况返回对应的结果
        if ((flag1 == -1) && (flag2 == 1)) {
            bridgestrnote[0] = "0"; // word1不存在，返回0
            return null;
        } else if ((flag1 == 1) && (flag2 == -1)) {
            bridgestrnote[0] = "1"; // word2不存在，返回1
            return null;
        } else if ((flag1 == -1) && (flag2 == -1)) {
            bridgestrnote[0] = "2"; // word1和word2都不存在，返回2
            return null;
        } else if ((flag1 == 1) && (flag2 == 1) && (flagc == 0)) {
            // word1和word2存在但没有桥接词连接它们
            return null;
        }

        return null; // 默认返回null
    }

    public String queryBridgeWords1(String word1,String word2) {
        int flag1=-1;
        int flag2=-1;
        for(int i=0;i<Varray.length;i++) {
            if(word1.equals(Varray[i].data)) {
                flag1=1;
            }
            if(word2.equals(Varray[i].data)) {
                flag2=1;
            }
        }
        int flagc=-1;
        if((flag1==1)&&(flag2==1)) {
            flagc=0;
            for(int i=0;i<Varray.length;i++) {
                if(word1.equals(Varray[i].data)) {
                    Enode pe=Varray[i].firstedge;
                    while((pe!=null)) {
                        String bridgestr=Varray[pe.serial].data;
                        for(int j=0;j<Varray.length;j++) {
                            if(bridgestr.equals(Varray[j].data)) {

                                Enode px=Varray[j].firstedge;
                                while((px!=null)) {
                                    if(Varray[px.serial].data.equals(word2)) {
                                        flagc=1;
                                        return bridgestr;
                                    }
                                    px=px.nextedge;
                                }
                            }
                        }
                        pe=pe.nextedge;
                    }
                }else ;
            }
        }

        // 根据不同情况返回对应的结果
        if((flag1==-1)&&(flag2==1)) {
            // word1不存在，返回0
            return null;
        }else if((flag1==1)&&(flag2==-1)){
            // word2不存在，返回1
            return null;
        }else if((flag1==-1)&&(flag2==-1)){
            // word1和word2都不存在，返回2
            return null;
        }else if((flag1==1)&&(flag2==1)&&(flagc==0)){
            // word1和word2存在但没有桥接词连接它们
            return null;
        }
        return null;
    }

    public String[] generateNewText(String sysinput) {
        // 将输入的字符串按空格拆分成数组
        String[] predealstrsys = sysinput.split(" ");

        // 打印数组中每个元素（调试用途）
        for (int i = 0; i < predealstrsys.length - 2; i++) {
            System.out.println(predealstrsys[i].toString());
        }

        // 创建一个新的字符串数组，长度为原数组长度减一
        String[] notestr = new String[predealstrsys.length - 1];

        // 遍历原数组，每次取两个相邻的单词调用queryBridgeWords1函数
        for (int i = 0; i < predealstrsys.length - 1; i++) {
            String word1 = predealstrsys[i];
            String word2 = predealstrsys[i + 1];
            // 调用queryBridgeWords1函数，将结果存入新数组中
            notestr[i] = queryBridgeWords1(word1, word2);
        }

        // 返回包含结果的字符串数组
        return notestr;
    }

    public int[][] Floyd(Matrix mt) {
        int size = mt.size;
        int[][] a = new int[size][size];
        int[][] path = new int[size][size];

        // 初始化数组a和path
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                path[i][j] = j;
                // 如果原矩阵中元素为0，将a中对应位置的值设为10000，否则为原矩阵中的值
                if (mt.maxArray[i][j] == 0) {
                    a[i][j] = 10000;
                } else {
                    a[i][j] = mt.maxArray[i][j];
                }
            }
        }

        // 使用Floyd算法计算最短路径
        for (int k = 0; k < size; k++) {
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    int temp = a[i][k] + a[k][j];
                    // 更新最短路径和路径信息
                    if (temp < a[i][j]) {
                        a[i][j] = temp;
                        path[i][j] = path[i][k];
                    }
                }
            }
        }

        // 返回最短路径信息数组path
        return path;
    }

    public String calcShortestPath(String word1, String word2, Matrix mt, int[][] path) {
        // 初始化最短路径字符串
        String shortestpath = new String();
        // 初始起点和终点设为-1
        int begin = -1;
        int over = -1;

        // 查找word1在哈希表中的索引作为起点
        for (int i = 0; i < mt.size; i++) {
            if (word1.equals(mt.hashkey[i])) {
                begin = i;
                break;
            }
        }

        // 查找word2在哈希表中的索引作为终点
        for (int i = 0; i < mt.size; i++) {
            if (word2.equals(mt.hashkey[i])) {
                over = i;
                break;
            }
        }

        // 如果起点和终点都找到了
        if (begin != -1 && over != -1) {
            int mid = path[begin][over];
            shortestpath = shortestpath + mt.hashkey[begin] + "->";
            shortestpath = shortestpath + mt.hashkey[mid];

            // 通过路径信息数组构建最短路径字符串
            while (mid != over) {
                begin = mid;
                mid = path[begin][over];
                shortestpath = shortestpath + "->" + mt.hashkey[mid];
            }

            // 添加换行符并返回最短路径字符串
            shortestpath = shortestpath + "\n";
            return shortestpath;
        } else if (begin == -1 && over != -1) {
            // 如果起点未找到，但终点找到了
            return "0"; // 返回错误码 "0"
        } else if (begin != -1 && over == -1) {
            // 如果起点找到了，但终点未找到
            return "1"; // 返回错误码 "1"
        } else if (begin == -1 && over == -1) {
            // 如果起点和终点都未找到
            return "2"; // 返回错误码 "2"
        } else {
            // 其他情况，返回错误码 "3"
            return "3";
        }
    }

    public String randomWalk(Matrix mt) {
        int size = mt.size;
        String dlstr = new String();

        // 创建一个副本矩阵用于记录访问状态
        int[][] copymt = new int[size][size];
        for(int i = 0;i < size;i++) {
            for(int j = 0;j<size;j++)
                copymt[i][j] = mt.maxArray[i][j];
        }

        // 生成一个随机起始点索引
        int randomx = (int)(Math.random() * 100 % 10);
        dlstr += mt.hashkey[randomx]; // 将起始点加入路径字符串

        int j=0;
        // 开始随机游走
        while(j<size) {
            // 如果从当前点到下一个点的路径不可达，则继续寻找下一个点
            if(copymt[randomx][j] != 1) {
                j++;
            }else {
                // 如果找到可达的下一个点，将其加入路径字符串，并更新矩阵的值
                dlstr = dlstr + " " + mt.hashkey[j];
                copymt[randomx][j] = -1; // 标记已访问
                randomx = j; // 更新当前点为下一个点
                j = 0; // 从头开始检查下一个点的连接
            }
        }
        return dlstr; // 返回随机游走生成的路径字符串
    }
}

class Graphv {
    public static void createDotGraph(String dotFormat,String fileName)
    {
        GraphViz gv=new GraphViz(); // 创建 GraphViz 对象
        gv.addln(gv.start_graph()); // 开始定义图形
        gv.add(dotFormat); // 添加图形的 DOT 格式内容
        gv.addln(gv.end_graph()); // 结束定义图形
        String type = "jpg"; // 指定图形输出的文件类型（这里是 jpg 格式）
        gv.increaseDpi(); // 调整图形的 DPI（像素密度），增加 DPI 值
        gv.decreaseDpi(); // 进一步降低 DPI 值
        File out = new File(fileName+"."+ type); // 指定输出文件的路径和文件名
        gv.writeGraphToFile( gv.getGraph( gv.getDotSource(), type ), out ); // 将图形内容写入文件
    }
}

class GraphViz {
    /**
     * Detects the client's operating system.
     */
    private final static String osName = System.getProperty("os.name").replaceAll("\\s","");

    /**
     * Load the config.properties file.
     */
    private final static String cfgProp = "config/config.properties";
    private final static Properties configFile = new Properties() {
        private final static long serialVersionUID = 1L; {
            try {
                load(new FileInputStream(cfgProp));
            } catch (Exception e) {}
        }
    };

    /**
     * The dir. where temporary files will be created.
     */
    private static String TEMP_DIR = "F:\\HIT\\计算机\\软件工程\\实验\\实验1 结对编程与Git\\JavaProjects";

    /**
     * Where is your dot program located? It will be called externally.
     */
    private static String DOT = "D:\\Program Files\\Graphviz\\bin\\dot.exe";

    /**
     * The image size in dpi. 96 dpi is normal size. Higher values are 10% higher each.
     * Lower values 10% lower each.
     *
     * dpi patch by Peter Mueller
     */
    private int[] dpiSizes = {46, 51, 57, 63, 70, 78, 86, 96, 106, 116, 128, 141, 155, 170, 187, 206, 226, 249};

    /**
     * Define the index in the image size array.
     */
    private int currentDpiPos = 7;

    /**
     * Increase the image size (dpi).
     */
    public void increaseDpi() {
        if ( this.currentDpiPos < (this.dpiSizes.length - 1) ) {
            ++this.currentDpiPos;
        }
    }

    /**
     * Decrease the image size (dpi).
     */
    public void decreaseDpi() {
        if (this.currentDpiPos > 0) {
            --this.currentDpiPos;
        }
    }

    public int getImageDpi() {
        return this.dpiSizes[this.currentDpiPos];
    }

    /**
     * The source of the graph written in dot language.
     */
    private StringBuilder graph = new StringBuilder();

    /**
     * Constructor: creates a new GraphViz object that will contain
     * a graph.
     */
    public GraphViz() {
    }

    /**
     * Returns the graph's source description in dot language.
     * @return Source of the graph in dot language.
     */
    public String getDotSource() {
        return this.graph.toString();
    }

    /**
     * Adds a string to the graph's source (without newline).
     */
    public void add(String line) {
        this.graph.append(line);
    }

    /**
     * Adds a string to the graph's source (with newline).
     */
    public void addln(String line) {
        this.graph.append(line + "\n");
    }

    /**
     * Adds a newline to the graph's source.
     */
    public void addln() {
        this.graph.append('\n');
    }

    public void clearGraph(){
        this.graph = new StringBuilder();
    }

    /**
     * Returns the graph as an image in binary format.
     * @param dot_source Source of the graph to be drawn.
     * @param type Type of the output image to be produced, e.g.: gif, dot, fig, pdf, ps, svg, png.
     * @return A byte array containing the image of the graph.
     */
    public byte[] getGraph(String dot_source, String type)
    {
        File dot;
        byte[] img_stream = null;

        try {
            dot = writeDotSourceToFile(dot_source);
            if (dot != null)
            {
                img_stream = get_img_stream(dot, type);
                if (dot.delete() == false)
                    System.err.println("Warning: " + dot.getAbsolutePath() + " could not be deleted!");
                return img_stream;
            }
            return null;
        } catch (java.io.IOException ioe) { return null; }
    }

    /**
     * Writes the graph's image in a file.
     * @param img   A byte array containing the image of the graph.
     * @param file  Name of the file to where we want to write.
     * @return Success: 1, Failure: -1
     */
    public int writeGraphToFile(byte[] img, String file)
    {
        File to = new File(file);
        return writeGraphToFile(img, to);
    }

    /**
     * Writes the graph's image in a file.
     * @param img   A byte array containing the image of the graph.
     * @param to    A File object to where we want to write.
     * @return Success: 1, Failure: -1
     */
    public int writeGraphToFile(byte[] img, File to)
    {
        try {
            FileOutputStream fos = new FileOutputStream(to);
            fos.write(img);
            fos.close();
        } catch (java.io.IOException ioe) { return -1; }
        return 1;
    }

    /**
     * It will call the external dot program, and return the image in
     * binary format.
     * @param dot Source of the graph (in dot language).
     * @param type Type of the output image to be produced, e.g.: gif, dot, fig, pdf, ps, svg, png.
     * @return The image of the graph in .gif format.
     */
    private byte[] get_img_stream(File dot, String type)
    {
        File img;
        byte[] img_stream = null;

        try {
            img = File.createTempFile("graph_", "."+type, new File(GraphViz.TEMP_DIR));
            Runtime rt = Runtime.getRuntime();

            // patch by Mike Chenault
            String[] args = {DOT, "-T"+type, "-Gdpi="+dpiSizes[this.currentDpiPos], dot.getAbsolutePath(), "-o", img.getAbsolutePath()};
            Process p = rt.exec(args);

            p.waitFor();

            FileInputStream in = new FileInputStream(img.getAbsolutePath());
            img_stream = new byte[in.available()];
            in.read(img_stream);
            // Close it if we need to
            if( in != null ) in.close();

            if (img.delete() == false)
                System.err.println("Warning: " + img.getAbsolutePath() + " could not be deleted!");
        }
        catch (java.io.IOException ioe) {
            System.err.println("Error:    in I/O processing of tempfile in dir " + GraphViz.TEMP_DIR+"\n");
            System.err.println("       or in calling external command");
            ioe.printStackTrace();
        }
        catch (java.lang.InterruptedException ie) {
            System.err.println("Error: the execution of the external program was interrupted");
            ie.printStackTrace();
        }

        return img_stream;
    }

    /**
     * Writes the source of the graph in a file, and returns the written file
     * as a File object.
     * @param str Source of the graph (in dot language).
     * @return The file (as a File object) that contains the source of the graph.
     */
    private File writeDotSourceToFile(String str) throws java.io.IOException
    {
        File temp;
        try {
            temp = File.createTempFile("dorrr",".dot", new File(GraphViz.TEMP_DIR));
            FileWriter fout = new FileWriter(temp);
            fout.write(str);
            BufferedWriter br=new BufferedWriter(new FileWriter("dotsource.dot"));
            br.write(str);
            br.flush();
            br.close();
            fout.close();
        }
        catch (Exception e) {
            System.err.println("Error: I/O error while writing the dot source to temp file!");
            return null;
        }
        return temp;
    }

    /**
     * Returns a string that is used to start a graph.
     * @return A string to open a graph.
     */
    public String start_graph() {
        return "digraph G {";
    }

    /**
     * Returns a string that is used to end a graph.
     * @return A string to close a graph.
     */
    public String end_graph() {
        return "}";
    }

    /**
     * Takes the cluster or subgraph id as input parameter and returns a string
     * that is used to start a subgraph.
     * @return A string to open a subgraph.
     */
    public String start_subgraph(int clusterid) {
        return "subgraph cluster_" + clusterid + " {";
    }

    /**
     * Returns a string that is used to end a graph.
     * @return A string to close a graph.
     */
    public String end_subgraph() {
        return "}";
    }

    /**
     * Read a DOT graph from a text file.
     *
     * @param input Input text file containing the DOT graph
     * source.
     */
    public void readSource(String input)
    {
        StringBuilder sb = new StringBuilder();

        try
        {
            FileInputStream fis = new FileInputStream(input);
            DataInputStream dis = new DataInputStream(fis);
            BufferedReader br = new BufferedReader(new InputStreamReader(dis));
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            dis.close();
        }
        catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
        }

        this.graph = sb;
    }
}